/*
 * Created by SharpDevelop.
 * User: mrose
 * Date: 6/6/2007
 * Time: 10:06 AM
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */

using System;
using System.Collections;
using System.Diagnostics;
using System.Windows.Forms;
using EnvDTE;

namespace Webbness.Module.VsTool
{
    /// <summary>
    /// Description of BuildIncrementer.
    /// </summary>
    public class BuildIncrementer
    {
        private DTE _dte;
        private string revisionNo;
        //private string[] defaultVersion;

        public BuildIncrementer(DTE dte)
        {
            _dte = dte;
            //revisionNo = GenerateRevisionNumber();
            //defaultVersion = new string[4]{"1", "1", "0", "*"};
        }

        public string RevisionNumber
        {
            get { return revisionNo; }
        }

        //' This event will be triggered after every build of a project
        //' You can modify the code below to only update projects that are active
        //' It currently will scan all projects in the solution for AssemblyInfo.cs files
        //' to update.
        public void IncrementBuildNumber_OLD()
        {
            AssemblyVersion version = new AssemblyVersion();

            if (_dte.Solution.SolutionBuild.LastBuildInfo != 0)
            {
                return;
            }
            int skippedProjects = 0;

            foreach (Project project in _dte.Solution.Projects)
            {
                ProjectItem assemblyInfoItem = GetAssemblyInfoItem(project);
                Boolean skipProject = false;
                if (assemblyInfoItem == null)
                {
                    skipProject = true;
                }

                if (skipProject)
                {
                    skippedProjects = skippedProjects + 1;
                }
                else
                {
                    string fileName = assemblyInfoItem.get_FileNames(1);
                    bool isUnderSCC = project.DTE.SourceControl.IsItemUnderSCC(fileName);
                    try
                    {
                        assemblyInfoItem.Open("{7651A701-06E5-11D1-8EBD-00A0C90F26EA}").Activate();
                        //'vsViewKindCode
                        TextDocument txtDoc = (TextDocument) assemblyInfoItem.Document.Object("TextDocument");
                        TextSelection foundText = GetBuildAndRevision(txtDoc, version);
                        if (foundText != null)
                        {
                            if (isUnderSCC)
                                CheckoutActiveItem();

                            IncrementIfNotEmptyOrStar(version);
                            SetToTimeOfDayIfNotEmptyOrStar(version, revisionNo);

                            foundText.Text = version.ToString();

                            assemblyInfoItem.Save(fileName);
                            if (isUnderSCC)
                                CheckinActiveItem();
                            _dte.ActiveWindow.Close(vsSaveChanges.vsSaveChangesYes);
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                }
            }

            Debug.WriteLine("Skipped " + skippedProjects + "projects");
        }

        //' This event will be triggered after every build of a project
        //' You can modify the code below to only update projects that are active
        //' It currently will scan all projects in the solution for AssemblyInfo.cs files
        //' to update.
        public void IncrementBuildNumber()
        {
            AssemblyVersion version = new AssemblyVersion();

            if (_dte.Solution.SolutionBuild.LastBuildInfo != 0)
            {
                return;
            }

            ArrayList items = new ArrayList();

            foreach (Project project in _dte.Solution.Projects)
            {
                try
                {
                    Property assemblyVersion = project.Properties.Item("AssemblyVersion");
                    if (assemblyVersion != null)
                    {
                        items.Add(new IncrementingAssemblyVersion(assemblyVersion.Value.ToString()));
                    }

                    Property assemblyFileVersion = project.Properties.Item("AssemblyFileVersion");
                    if (assemblyFileVersion != null)
                    {
                        items.Add(new IncrementingAssemblyVersion(assemblyFileVersion.Value.ToString()));
                    }
                }
                catch (Exception e)
                {
                    Debug.WriteLine(e.Message);
                    //bury the error and keep going...
                }
            }

            items.Sort();

            IncrementingAssemblyVersion maxVersion = items[0] as IncrementingAssemblyVersion;
            maxVersion.Increment();
            revisionNo = maxVersion.ToString();

            foreach (Project project in _dte.Solution.Projects)
            {
                try
                {
                    Property assemblyVersion = project.Properties.Item("AssemblyVersion");
                    Property assemblyFileVersion = project.Properties.Item("AssemblyFileVersion");
                    assemblyVersion.Value = revisionNo;
                    assemblyFileVersion.Value = revisionNo;
                    project.Save(null);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                }
            }
        }

        //'Returns the full path of the first file
        //'in the project with the name AssemblyInfo
        protected virtual ProjectItem GetAssemblyInfoItem(Project project)
        {
            foreach (ProjectItem projectItem in project.ProjectItems)
            {
                for (short i = 0; i < projectItem.FileCount; i++)
                {
                    if (IsMeaningfulProject(projectItem.Kind))
                    {
                        string filepath = projectItem.get_FileNames(i);

                        int indexOfLastSep = 1;
                        indexOfLastSep = filepath.LastIndexOf(@"\");
                        if (indexOfLastSep < 1)
                            indexOfLastSep = 1;

                        String fileName = filepath.Substring(indexOfLastSep);

                        if (fileName.IndexOf("AssemblyInfo") > 0)
                            return projectItem;
                    }
                }
            }

            return null;
        }


        protected bool IsMeaningfulProject(string projectKind)
        {
            switch (projectKind)
            {
                case Constants.vsProjectItemKindSolutionItems:
                    return false;
            }
            return true;
        }

        protected virtual TextSelection GetBuildAndRevision(TextDocument textDocument, AssemblyVersion version)
        {
            string[] defaultVersion = new string[4] {"1", "1", "0", "*"};

            TextSelection objSelection = textDocument.Selection;

            //Get the text between AssemblyVersion(" and ")
            if (objSelection.FindText("AssemblyVersion(\"", (int) vsFindOptions.vsFindOptionsNone) == false)
                return null;

            int objStartPosition = objSelection.BottomPoint.DisplayColumn;

            if (objSelection.FindText("\")]", (int) vsFindOptions.vsFindOptionsNone) == false)
                return null;

            int objEndPosition = objSelection.AnchorPoint.DisplayColumn;

            //Get back to after the AssemblyVersion" part
            objSelection.StartOfLine(vsStartOfLineOptions.vsStartOfLineOptionsFirstColumn, false);
            objSelection.FindText("AssemblyVersion(\"", (int) vsFindOptions.vsFindOptionsNone);
            objSelection.Collapse();
            objSelection.MoveToDisplayColumn(0, objEndPosition, true);

            string[] strTemp = objSelection.Text.Split('.');

            for (int i = 0; i < strTemp.Length; i++)
            {
                defaultVersion[i] = strTemp[i];
            }

            version.Major = defaultVersion[0];
            version.Minor = defaultVersion[1];
            version.Build = defaultVersion[2];
            version.Revision = defaultVersion[3];

            return objSelection;
        }


        //'Generates a revision number for this build by using the curent time
        protected virtual string GenerateRevisionNumber()
        {
            DateTime now = DateTime.Now;
            String hrs = now.Hour.ToString();
            String mins = now.Minute.ToString();
            String secs = now.Second.ToString();

            return hrs + mins + secs;
        }

        //'Retrieves the text from the range and if it star or empty then
        //'returns otherwise increments it
        protected virtual void IncrementIfNotEmptyOrStar(AssemblyVersion version)
        {
            if (version.Build.Equals("*"))
                return;
            try
            {
                int buildNo = Convert.ToInt32(version.Build);
                buildNo++;
                version.Build = buildNo.ToString();
            }
            catch
            {
                //bury the error and do nothing
            }
        }

        //'Retrieves the text from the range and if it star or empty then
        //'returns otherwise sets it to the time of day
        protected virtual void SetToTimeOfDayIfNotEmptyOrStar(AssemblyVersion version, String revisionNo)
        {
            if (version.Revision.Equals("*"))
                return;
            version.Revision = revisionNo;
        }

        protected virtual void CheckoutActiveItem()
        {
            Command checkoutCmd = _dte.Commands.Item("File.CheckOutDynamicSilent", 0);
            //'Check if the item is already checked out or not
            if (!checkoutCmd.IsAvailable)
            {
                Debug.WriteLine("Checkout cmd not available");

                return;
            }

            object customin = null;
            object customout = null;
            try
            {
                _dte.Commands.Raise(checkoutCmd.Guid, checkoutCmd.ID, ref customin, ref customout);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }

        protected virtual void CheckinActiveItem()
        {
            Command checkinCmd = _dte.Commands.Item("File.CheckInDynamicSilent", 0);
            //'Check if the item is already checked out or not
            if (!checkinCmd.IsAvailable)
            {
                Debug.WriteLine("Checkin cmd not available, something is wrong");
                return;
            }
            object customin = null;
            object customout = null;
            _dte.Commands.Raise(checkinCmd.Guid, checkinCmd.ID, ref customin, ref customout);
        }

        #region Nested type: IncrementingAssemblyVersion

        public class IncrementingAssemblyVersion : IComparable
        {
            private static string[] defaultVersion = new string[4] {"0", "1", "0", "0"};
            public int Build;
            public int Major;
            public int Minor;
            public int Revision;

            public IncrementingAssemblyVersion(string value)
            {
                string[] strTemp = value.Split('.');
                for (int i = 0; i < strTemp.Length; i++)
                {
                    defaultVersion[i] = strTemp[i];
                }
                Major = SetValue(defaultVersion[0]);
                Minor = SetValue(defaultVersion[1]);
                Build = SetValue(defaultVersion[2]);
                Revision = SetValue(defaultVersion[3]);
            }

            #region IComparable Members

            public int CompareTo(object obj)
            {
                IncrementingAssemblyVersion other = obj as IncrementingAssemblyVersion;
                if (other == null)
                {
                    return String.Compare(ToString(), obj.ToString());
                }

                if (Major > other.Major)
                    return -1;
                if (Major < other.Major)
                    return 1;
                if (Minor > other.Minor)
                    return -1;
                if (Minor < other.Minor)
                    return 1;
                if (Build > other.Build)
                    return -1;
                if (Build < other.Build)
                    return 1;
                if (Revision > other.Revision)
                    return -1;
                if (Revision < other.Revision)
                    return 1;
                return 0;
            }

            #endregion

            public override string ToString()
            {
                return Major + "." + Minor + "." + Build + "." + Revision;
            }

            public void SetMajor(string value)
            {
                Major = SetValue(value);
            }

            public void SetMinor(string value)
            {
                Minor = SetValue(value);
            }

            public void SetBuild(string value)
            {
                Build = SetValue(value);
            }

            public void SetRevision(string value)
            {
                Revision = SetValue(value);
            }

            public void Increment()
            {
                Revision++;
                if (Revision >= 9999)
                {
                    Revision = 0;
                    Build++;
                }
                if (Build >= 9999)
                {
                    Build = 0;
                    Minor++;
                }
                if (Minor >= 9999)
                {
                    Minor = 0;
                    Major++;
                }
            }

            private int SetValue(String value)
            {
                try
                {
                    return Convert.ToInt32(value);
                }
                catch
                {
                    return 0;
                }
            }
        }

        #endregion
    }
}